# Copyright (c) 2025 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache Software License 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
# which is available at https://opensource.org/licenses/MIT.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT

#[=======================================================================[.rst:
GdbPrettyPrinter
----------------

This module provides a command for adding an embedded gdb pretty printer
Python script to a target. All resulting binaries will automatically use
the embedded pretty printers, as long as gdb is permitted to load them
(e.g. because of an add-auto-load-safe-path entry in .gdbinit).

Commands
^^^^^^^^

This module provides the following commands:

* :command:`target_gdb_pretty_printer`

  .. code-block:: cmake

    target_gdb_pretty_printer(
      <target>
      <INTERFACE|PUBLIC|PRIVATE> <pretty-printer-file-path>...
      [<INTERFACE|PUBLIC|PRIVATE> <pretty-printer-file-path>... ]...
    )

This command adds the necessary build steps to embed the pretty printer
script at `<pretty-printer-file-path>` into `target`.

It will create a custom target
`<target>_pretty_printer_<pretty_printer_name>` that contains all build
steps for producing the object file containing the gdb pretty printer
data. That object file will then get added to `target` as a library link
input.

This function does nothing unless you are building natively on Linux and
ICEORYX2_EMBED_GDB_PRETTY_PRINTER is set. This function may fail to perform
correctly if using another linker than the GNU binutils `ld` (BFD) linker.

#]=======================================================================]

function(_find_gnu_binutils_ld)
    set(GNU_BINUTILS_LD_FOUND FALSE)
    find_program(ICEORYX2_GNU_BINUTILS_LD NAMES ld)
    mark_as_advanced(ICEORYX2_GNU_BINUTILS_LD)
    if (ICEORYX2_GNU_BINUTILS_LD)
        execute_process(COMMAND ${ICEORYX2_GNU_BINUTILS_LD} --version
            OUTPUT_VARIABLE gnuld_version_out
        )
        if (gnuld_version_out MATCHES "^GNU ld \\(GNU Binutils\\) (.*)")
            set(GNU_BINUTILS_LD_FOUND TRUE)
        endif()
    endif()
    set(GNU_BINUTILS_LD_FOUND ${GNU_BINUTILS_LD_FOUND} PARENT_SCOPE)
endfunction(_find_gnu_binutils_ld)


function(_prepare_pretty_printer_object_file target scope pretty_printer_file_path)
    if (NOT EXISTS ${pretty_printer_file_path})
        message(FATAL_ERROR "Pretty printer source file not found ${pretty_printer_file_path}")
    endif()
    get_filename_component(pretty_printer_filename ${pretty_printer_file_path} NAME)
    get_filename_component(pretty_printer_name ${pretty_printer_file_path} NAME_WE)
    if (TARGET ${target}_pretty_printer_${pretty_printer_name})
        message(FATAL_ERROR "Cannot add the same pretty printer "
                            "${pretty_printer_file_path} more than once "
                            "to the same target ${target}.")
    endif()
    # The object file generated by this function is a relocatable elf file with a single
    # .debug_gdb_scripts section that contains in order:
    #  - A leading byte 0x04 (identifying the section as SECTION_SCRIPT_ID_PYTHON_TEXT)
    #  - The file name of the pretty printer script
    #  - A single line-feed character (0x0a)
    #  - The contents of the pretty printer Python file
    #  - A single null terminator byte (0x00)
    # The script sets up individual object files for the filename and script content
    # and then links them together to a single object file. A linker script ensures
    # the correct layout of the section contents. The resulting object file will
    # be added as a link dependency to the target.
    set(output_dir ${PROJECT_BINARY_DIR}/pretty_printer_files/${target}/${pretty_printer_name})
    add_custom_command(
        OUTPUT ${output_dir}/filename.raw
        COMMAND ${CMAKE_COMMAND}
            -DOUTPUT_FILE_NAME=${output_dir}/filename.raw
            -DOUTPUT_FILE_CONTENTS=${pretty_printer_filename}
            -P ${PROJECT_SOURCE_DIR}/cmake/resources/write_file_script.cmake
        VERBATIM
    )
    add_custom_command(
        OUTPUT ${output_dir}/pretty_printer_filename.o
        COMMAND ${ICEORYX2_GNU_BINUTILS_LD} -r -b binary -o ${output_dir}/pretty_printer_filename.o
        ${output_dir}/filename.raw
        DEPENDS ${output_dir}/filename.raw
        VERBATIM
    )
    add_custom_command(
        OUTPUT ${output_dir}/pretty_printer_script.o
        COMMAND ${ICEORYX2_GNU_BINUTILS_LD} -r -b binary -o ${output_dir}/pretty_printer_script.o
        ${pretty_printer_file_path}
        DEPENDS ${pretty_printer_file_path}
        VERBATIM
    )
    add_custom_command(
        OUTPUT ${output_dir}/${pretty_printer_name}.o
        COMMAND ${ICEORYX2_GNU_BINUTILS_LD} -r -o ${output_dir}/${pretty_printer_name}.o
        ${output_dir}/pretty_printer_filename.o
        ${output_dir}/pretty_printer_script.o
        -T ${PROJECT_SOURCE_DIR}/cmake/resources/linker_script.ld
        DEPENDS
        ${output_dir}/pretty_printer_filename.o
        ${output_dir}/pretty_printer_script.o
        ${PROJECT_SOURCE_DIR}/cmake/resources/linker_script.ld
        VERBATIM
    )
    add_custom_target(${target}_pretty_printer_${pretty_printer_name}
        COMMENT "Embedding gdb pretty printer script ${pretty_printer_name}"
        DEPENDS ${output_dir}/${pretty_printer_name}.o)

    add_dependencies(${target} ${target}_pretty_printer_${pretty_printer_name})
    target_link_libraries(${target} ${scope} ${output_dir}/${pretty_printer_name}.o)
endfunction(_prepare_pretty_printer_object_file)


function(target_gdb_pretty_printer target scope pretty_printer_file_path)
    if (NOT TARGET ${target})
        message(FATAL_ERROR "Cannot embed pretty printer in '${target}' - not a target.")
    endif()
    if ((NOT ICEORYX2_EMBED_GDB_PRETTY_PRINTER) OR (NOT LINUX) OR CMAKE_CROSSCOMPILING)
        return()
    endif()
    _find_gnu_binutils_ld()
    if (NOT GNU_BINUTILS_LD_FOUND)
        message(FATAL_ERROR "ICEORYX2_GNU_BINUTILS_LD must point to GNU Binutils ld")
    endif()
    cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "INTERFACE;PUBLIC;PRIVATE")
    if (arg_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Invalid arguments ${arg_UNPARSED_ARGUMENTS}")
    endif()

    foreach(scope INTERFACE PUBLIC PRIVATE)
        foreach(f ${arg_${scope}})
            _prepare_pretty_printer_object_file(${target} ${scope} ${f})
        endforeach()
    endforeach()
endfunction(target_gdb_pretty_printer)
